                              //OCRPanel.cpp//                                
//////////////////////////////////////////////////////////////////////////////////
//																				//
// Author:  Simeon Kosnitsky													//
//          skosnits@gmail.com													//
//																				//
// License:																		//
//     This software is released into the public domain.  You are free to use	//
//     it in any way you like, except that you may not sell this source code.	//
//																				//
//     This software is provided "as is" with no expressed or implied warranty.	//
//     I accept no liability for any damage or loss of business that this		//
//     software may cause.														//
//																				//
//////////////////////////////////////////////////////////////////////////////////

#define _HAS_STD_BYTE 0
#include "OCRPanel.h"
#include "CommonFunctions.h"
#include <algorithm>
#include <vector>
#include <regex>
#include <fstream>
#include <streambuf>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include <wx/regex.h>
#include <wx/sound.h>
#include <wx/rawbmp.h>

using namespace std;

bool g_use_ISA_images_for_get_txt_area = true;
bool g_use_ILA_images_for_get_txt_area = true;

bool g_join_subs_and_correct_time = true;
bool g_clear_txt_folders = true;

int g_IsCreateClearedTextImages = 0;
int g_RunCreateClearedTextImages = 0;
int g_IsJoinTXTImages = 0;
int g_RunJoinTXTImages = 0;
bool g_ValidateAndCompareTXTImages = false;
bool g_DontDeleteUnrecognizedImages1 = true;
bool g_DontDeleteUnrecognizedImages2 = true;

wxString g_DefStringForEmptySub;

bool g_CLEAN_RGB_IMAGES = false;

int  g_ocr_threads = 8;

void GetFileNames(wxString dir_path, vector<wxString>& FileNamesVector);

wxDEFINE_EVENT(UPDATE_CCTI_PROGRESS, wxThreadEvent);
wxDEFINE_EVENT(THREAD_CCTI_END, wxCommandEvent);
wxDEFINE_EVENT(THREAD_JOIN_TXT_IMAGES_END, wxCommandEvent);

AssTXTLine::AssTXTLine()
{
	m_LH = 0;
	m_LY = 0;
	m_LXB = 0;
	m_LXE = 0;
	m_LYB = 0;
	m_LYE = 0;

	m_mY = 0;
	m_mI = 0;
	m_mQ = 0;

	m_BT = 0;
	m_ET = 0;

	m_pAssStyle = NULL;

	m_dX = -1;
	m_dY = -1;
	m_Alignment = -1;
}

AssTXTLine& AssTXTLine::operator=(const AssTXTLine &other)
{
	m_TXTStr = other.m_TXTStr;
	m_LH = other.m_LH;
	m_LY = other.m_LY;
	m_LXB = other.m_LXB;
	m_LXE = other.m_LXE;
	m_LYB = other.m_LYB;
	m_LYE = other.m_LYE;

	m_mY = other.m_mY;
	m_mI = other.m_mI;
	m_mQ = other.m_mQ;

	m_BT = other.m_BT;
	m_ET = other.m_ET;

	m_pAssStyle = other.m_pAssStyle;

	m_dX = other.m_dX;
	m_dY = other.m_dY;
	m_Alignment = other.m_Alignment;

	return *this;
}

AssTXTStyle::AssTXTStyle()
{	
	m_data.clear();

	m_minY = 0;
	m_minI = 0;
	m_minQ = 0;
	
	m_maxY = 0;
	m_maxI = 0;
	m_maxQ = 0;

	m_mY = 0;
	m_mI = 0;
	m_mQ = 0;

	m_minLH = 0;
	m_maxLH = 0;

	m_LH = 0;

	m_Alignment = 2;
	m_MarginL = 10;
	m_MarginR = 10;
	m_MarginV = 10;
}

// W - full image include scale (if is) width
// H - full image include scale (if is) height
void AssTXTStyle::Compute(int W, int H)
{
	int i;
	int size, val1, val2, val3, val4;

	val1 = 0;
	val2 = 0;
	val3 = 0;
	val4 = 0;

	size = (int)m_data.size();

	for (i=0; i<size; i++)
	{
		val1 += m_data[i].m_mY;
		val2 += m_data[i].m_mI;
		val3 += m_data[i].m_mQ;
		val4 += m_data[i].m_LH;
	}
	

	m_mY = val1/size;
	m_mI = val2/size;
	m_mQ = val3/size;
	m_LH = (val4*528*100)/(size*H*53);
	m_LH += m_LH%2;
}

wxString AssSubHead =
"[Script Info]\n\
; Script generated by VideoSubFinder\n\
; http://www.aegisub.org/\n\
Title: Default Aegisub file\n\
ScriptType: v4.00+\n\
WrapStyle: 0\n\
ScaledBorderAndShadow: yes\n\
YCbCr Matrix: None\n\
\n\
[Aegisub Project Garbage]\n\
\n\
[V4+ Styles]\n\
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n\
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1\n\
\n\
[Events]\n\
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n";

BEGIN_EVENT_TABLE(COCRPanel, wxPanel)
	EVT_COMMAND(wxID_ANY, THREAD_CCTI_END, COCRPanel::ThreadCreateClearedTextImagesEnd)
	EVT_COMMAND(wxID_ANY, THREAD_JOIN_TXT_IMAGES_END, COCRPanel::ThreadJoinTXTImagesThreadEnd)
	EVT_BUTTON(ID_BTN_CES, COCRPanel::OnBnClickedCreateEmptySub)
	EVT_BUTTON(ID_BTN_CSCTI, COCRPanel::OnBnClickedCreateSubFromClearedTXTImages)
	EVT_BUTTON(ID_BTN_CSTXT, COCRPanel::OnBnClickedCreateSubFromTXTResults)
	EVT_BUTTON(ID_BTN_CCTI, COCRPanel::OnBnClickedCreateClearedTextImages)
	EVT_BUTTON(ID_BTN_JOIN, COCRPanel::OnBnClickedJoinTXTImages)
END_EVENT_TABLE()

COCRPanel::COCRPanel(CSSOWnd* pParent)
		:wxPanel( pParent, wxID_ANY )
{
	m_pParent = pParent;
	m_pMF = pParent->m_pMF;
}

COCRPanel::~COCRPanel()
{
}

void COCRPanel::Init()
{
	SaveToReportLog("COCRPanel::Init(): starting...\n");

	wxRect rcCCTI, rcCES, rcP3, rcClP3, rlMSD, reMSD, rlJTXTSL, reJTXTSL, rlJSACT, rlCTXTF, rlSESS, rlSSI, rcTEST, rcCSCTI, rcCSTXT, rcJOIN;
	int w, h, cbh, dw, dh, txt_dw = g_cfg.m_txt_dw, txt_dy = g_cfg.m_txt_dy;
	const int dx = m_dx;
	const int dy = 20;
	const int cb_dist = 3;
	const int btn_dist = 8;
	const int BTNW = 410;
	const int LBLW = 250;
	const int PW = BTNW + LBLW + dx*3;

	h = 26;
	cbh = 22;

	rlMSD.x = (PW - BTNW - LBLW - dx) / 2;
	rlMSD.y = dy;
	rlMSD.width = LBLW;
	rlMSD.height = cbh;

	rcCCTI.x = rlMSD.GetRight() + dx;
	rcCCTI.y = dy;
	rcCCTI.width = BTNW;
	rcCCTI.height = h;

	rcCSTXT.x = rcCCTI.x;
	rcCSTXT.y = rcCCTI.GetBottom() + btn_dist;
	rcCSTXT.width = BTNW;
	rcCSTXT.height = h;

	rcCSCTI.x = rcCCTI.x;
	rcCSCTI.y = rcCSTXT.GetBottom() + btn_dist;
	rcCSCTI.width = BTNW;
	rcCSCTI.height = h;

	rcCES.x = rcCCTI.x;
	rcCES.y = rcCSCTI.GetBottom() + btn_dist;
	rcCES.width = BTNW;
	rcCES.height = h;

	rcJOIN.x = rcCCTI.x;
	rcJOIN.y = rcCES.GetBottom() + btn_dist;
	rcJOIN.width = BTNW;
	rcJOIN.height = h;

	rcTEST.x = rcCCTI.GetRight() + 30;
	rcTEST.y = rcCCTI.GetBottom() + 5 - h/2;
	rcTEST.width = 100;
	rcTEST.height = h;	

	reMSD.x = rlMSD.x;
	reMSD.y = rlMSD.GetBottom() + cb_dist;
	reMSD.width = rlMSD.width;
	reMSD.height = cbh;

	rlJTXTSL.x = reMSD.x;
	rlJTXTSL.y = reMSD.GetBottom() + cb_dist;
	rlJTXTSL.width = reMSD.width;
	rlJTXTSL.height = cbh;

	reJTXTSL.x = rlJTXTSL.x;
	reJTXTSL.y = rlJTXTSL.GetBottom() + cb_dist;
	reJTXTSL.width = rlJTXTSL.width;
	reJTXTSL.height = cbh;

	rlJSACT.x = reJTXTSL.x;
	rlJSACT.y = reJTXTSL.GetBottom() + cb_dist;
	rlJSACT.width = reJTXTSL.width;
	rlJSACT.height = cbh;	

	rlCTXTF.x = rlJSACT.x;
	rlCTXTF.y = rlJSACT.GetBottom() + cb_dist;
	rlCTXTF.width = rlJSACT.width;
	rlCTXTF.height = cbh;

	rlSESS.x = rlCTXTF.x;
	rlSESS.y = rlCTXTF.GetBottom() + cb_dist;
	rlSESS.width = rlCTXTF.width;
	rlSESS.height = cbh;
	
	rlSSI.x = rlSESS.x;
	rlSSI.y = rlSESS.GetBottom() + cb_dist;
	rlSSI.width = rlSESS.width;
	rlSSI.height = cbh;

	rcP3 = this->GetRect();

	this->GetClientSize(&w, &h);
	rcClP3.x = rcClP3.y = 0; 
	rcClP3.width = w;
	rcClP3.height = h;

	dw = rcP3.width - rcClP3.width;
	dh = rcP3.height - rcClP3.height;

	rcP3.x = 0;	
	rcP3.y = 0;
	rcP3.width = PW + dw;
	rcP3.height = rlSSI.GetBottom() + dy + dh;

	SaveToReportLog("COCRPanel::Init(): this->SetSize(rcP3)...\n");
	this->SetSize(rcP3);

	SaveToReportLog("COCRPanel::Init(): init m_pP3...\n");
	m_pP3 = new wxPanel( this, wxID_ANY, rcP3.GetPosition(), rcP3.GetSize() );
	wxSize p3_min_size = rcP3.GetSize();
	m_pP3->SetMinSize(p3_min_size);
	m_pP3->SetBackgroundColour(g_cfg.m_notebook_panels_colour);

	SaveToReportLog("COCRPanel::Init(): init m_pGB...\n");
	g_cfg.m_ocr_GB_label = wxT("");
	m_pGB = new CStaticBox(m_pP3, wxID_ANY, g_cfg.m_ocr_GB_label);
	m_pGB->SetFont(m_pMF->m_LBLFont);
	m_pGB->SetTextColour(g_cfg.m_main_text_colour);
	m_pGB->SetBackgroundColour(g_cfg.m_notebook_panels_colour);

	SaveToReportLog("COCRPanel::Init(): init m_pDG...\n");
	m_pDG = new CDataGrid(m_pGB, g_cfg.m_grid_col_property_label, g_cfg.m_grid_col_value_label, wxID_ANY, &(m_pMF->m_LBLFont), &(g_cfg.m_main_text_colour));
	m_pDG->SetBackgroundColour(g_cfg.m_notebook_colour);
	m_pDG->SetGridLineColour(g_cfg.m_grid_line_colour);

	m_pDG->AddSubGroup(g_cfg.m_ocr_dg_sub_group_settings_for_create_txt_images, g_cfg.m_grid_sub_gropes_colour);
	m_pDG->AddProperty(g_cfg.m_ocr_label_clear_txt_folders, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &g_clear_txt_folders);
	m_pDG->AddProperty(g_cfg.m_ocr_label_save_each_substring_separately, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &g_save_each_substring_separately);
	m_pDG->AddProperty(g_cfg.m_ocr_label_save_scaled_images, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &g_save_scaled_images);

	m_pDG->AddSubGroup(g_cfg.m_ocr_dg_sub_group_settings_for_join_images, g_cfg.m_grid_sub_gropes_colour);	
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_join_rgb_images, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_join_rgb_images));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_use_txt_images_data_for_join_rgb_images, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_clear_dir, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_clear_dir));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_split_line_text, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_split_line));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_split_line_font_size, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_split_line_font_size), -1, g_max_font_size);
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_split_line_font_bold, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_split_line_font_bold));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_sub_id_format, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_sub_id_format));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_sub_search_by_id_format, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_sub_search_by_id_format));
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_scale, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_scale), 1, 4);
	m_pDG->AddProperty(g_cfg.m_ocr_label_join_images_max_number, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_join_images_max_number), 2, 5000);

	m_pDG->AddSubGroup(g_cfg.m_ocr_dg_sub_group_settings_for_create_sub, g_cfg.m_grid_sub_gropes_colour);
	m_pDG->AddProperty(g_cfg.m_ocr_label_msd_text, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &(g_cfg.m_ocr_min_sub_duration), 0.0, 100.0);
	m_pDG->AddProperty(g_cfg.m_ocr_label_jsact_text, g_cfg.m_main_labels_background_colour, g_cfg.m_main_text_ctls_background_colour, &g_join_subs_and_correct_time);

	SaveToReportLog("COCRPanel::Init(): init m_pCCTI...\n");
	m_pCCTI = new CButton(m_pP3, ID_BTN_CCTI, g_cfg.m_main_buttons_colour, g_cfg.m_main_buttons_colour_focused, g_cfg.m_main_buttons_colour_selected, g_cfg.m_main_buttons_border_colour,
		g_cfg.m_ocr_button_ccti_text, rcCCTI.GetPosition(), rcCCTI.GetSize());
	m_pCCTI->SetFont(m_pMF->m_BTNFont);
	m_pCCTI->SetTextColour(g_cfg.m_main_text_colour);

	SaveToReportLog("COCRPanel::Init(): init m_pCSTXT...\n");
	m_pCSTXT = new CButton(m_pP3, ID_BTN_CSTXT, g_cfg.m_main_buttons_colour, g_cfg.m_main_buttons_colour_focused, g_cfg.m_main_buttons_colour_selected, g_cfg.m_main_buttons_border_colour,
		g_cfg.m_ocr_button_csftr_text, rcCSTXT.GetPosition(), rcCSTXT.GetSize());
	m_pCSTXT->SetFont(m_pMF->m_BTNFont);
	m_pCSTXT->SetTextColour(g_cfg.m_main_text_colour);

	SaveToReportLog("COCRPanel::Init(): init m_pCSCTI...\n");
	m_pCSCTI = new CButton(m_pP3, ID_BTN_CSCTI, g_cfg.m_main_buttons_colour, g_cfg.m_main_buttons_colour_focused, g_cfg.m_main_buttons_colour_selected, g_cfg.m_main_buttons_border_colour,
		g_cfg.m_ocr_button_cesfcti_text, rcCSCTI.GetPosition(), rcCSCTI.GetSize());
	m_pCSCTI->SetFont(m_pMF->m_BTNFont);
	m_pCSCTI->SetTextColour(g_cfg.m_main_text_colour);

	SaveToReportLog("COCRPanel::Init(): init m_pCES...\n");
	m_pCES = new CButton( m_pP3, ID_BTN_CES, g_cfg.m_main_buttons_colour, g_cfg.m_main_buttons_colour_focused, g_cfg.m_main_buttons_colour_selected, g_cfg.m_main_buttons_border_colour,
		g_cfg.m_ocr_button_ces_text, rcCES.GetPosition(), rcCES.GetSize());
	m_pCES->SetFont(m_pMF->m_BTNFont);
	m_pCES->SetTextColour(g_cfg.m_main_text_colour);
	
	SaveToReportLog("COCRPanel::Init(): init m_pJOIN...\n");
	m_pJOIN = new CButton(m_pP3, ID_BTN_JOIN, g_cfg.m_main_buttons_colour, g_cfg.m_main_buttons_colour_focused, g_cfg.m_main_buttons_colour_selected, g_cfg.m_main_buttons_border_colour,
		g_cfg.m_ocr_button_join_text, rcJOIN.GetPosition(), rcJOIN.GetSize());
	m_pJOIN->SetFont(m_pMF->m_BTNFont);
	m_pJOIN->SetTextColour(g_cfg.m_main_text_colour);

	this->Bind(UPDATE_CCTI_PROGRESS, &COCRPanel::OnUpdateCCTIProgress, this);

	// m_pP3 location sizer
	{
		wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL);
		wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
		button_sizer->Add(m_pP3, 1, wxALIGN_CENTER, 0);
		top_sizer->Add(button_sizer, 1, wxALIGN_CENTER);
		SaveToReportLog("COCRPanel::Init(): this->SetSizer(top_sizer)...\n");
		this->SetSizer(top_sizer);
	}

	// m_pP3 elements location sizer
	{
		wxBoxSizer* gb_vert_box_sizer = new wxBoxSizer(wxVERTICAL);
		m_gb_hor_box_sizer = new wxBoxSizer(wxHORIZONTAL);
		wxStaticBoxSizer *gb_sizer = new wxStaticBoxSizer(m_pGB, wxVERTICAL);

		m_gb_hor_box_sizer->Add(m_pDG, 1, wxEXPAND | wxALL);
		gb_vert_box_sizer->Add(m_gb_hor_box_sizer, 1, wxEXPAND | wxALL, 4);
		gb_sizer->Add(gb_vert_box_sizer);

		m_vert_box_buttons_sizer = new wxBoxSizer(wxVERTICAL);
		m_pSpacerBNs = m_vert_box_buttons_sizer->AddSpacer(1);
		m_vert_box_buttons_sizer->Add(m_pCCTI, 0, wxEXPAND | wxALL);
		m_vert_box_buttons_sizer->AddSpacer(btn_dist);
		m_vert_box_buttons_sizer->Add(m_pCSTXT, 0, wxEXPAND | wxALL);
		m_vert_box_buttons_sizer->AddSpacer(btn_dist);
		m_vert_box_buttons_sizer->Add(m_pCSCTI, 0, wxEXPAND | wxALL);
		m_vert_box_buttons_sizer->AddSpacer(btn_dist);
		m_vert_box_buttons_sizer->Add(m_pCES, 0, wxEXPAND | wxALL);
		m_vert_box_buttons_sizer->AddSpacer(btn_dist);
		m_vert_box_buttons_sizer->Add(m_pJOIN, 0, wxEXPAND | wxALL);

		wxBoxSizer* hor_box_all_ctrls_sizer = new wxBoxSizer(wxHORIZONTAL);
		hor_box_all_ctrls_sizer->AddSpacer(dx/2);
		hor_box_all_ctrls_sizer->Add(gb_sizer, 0, wxALIGN_TOP);
		hor_box_all_ctrls_sizer->AddSpacer(dx);
		hor_box_all_ctrls_sizer->Add(m_vert_box_buttons_sizer, 0, wxALIGN_TOP);
		hor_box_all_ctrls_sizer->AddSpacer(dx/2);

		wxBoxSizer* vert_box_sizer = new wxBoxSizer(wxVERTICAL);
		wxBoxSizer* hor_box_sizer = new wxBoxSizer(wxHORIZONTAL);

		hor_box_sizer->Add(hor_box_all_ctrls_sizer, 0, wxALIGN_CENTER);
		vert_box_sizer->Add(hor_box_sizer, 1, wxALIGN_CENTER);

		m_pP3->SetSizer(vert_box_sizer);
	}

	SaveToReportLog("COCRPanel::Init(): finished.\n");
}

void COCRPanel::UpdateSize()
{
	wxSize client_size = this->GetClientSize();

	wxSize dg_opt_size = m_pDG->GetOptimalSize();
	dg_opt_size.x += 10;
	dg_opt_size.y = client_size.y - 10;
	bool res = m_gb_hor_box_sizer->SetItemMinSize(m_pDG, dg_opt_size);
	
	wxSize p3_best_size = m_pP3->GetSizer()->GetMinSize();

	wxSize p3_cur_size = m_pP3->GetSize();
	wxSize p3_cur_client_size = m_pP3->GetClientSize();
	p3_best_size.x += p3_cur_size.x - p3_cur_client_size.x + 20;
	p3_best_size.y += p3_cur_size.y - p3_cur_client_size.y + 20;

	if (p3_best_size.x > client_size.x - 8)
	{
		wxSize gb_cur_size = m_pGB->GetSize();
		wxSize dg_cur_size = m_pDG->GetSize();

		wxSize buttons_best_size = m_vert_box_buttons_sizer->GetMinSize();
		dg_opt_size.x = std::max<int>(client_size.x - 8 - buttons_best_size.x - 2 * m_dx - (gb_cur_size.x - dg_cur_size.x), 50);
		m_gb_hor_box_sizer->SetItemMinSize(m_pDG, dg_opt_size);
	}

	p3_best_size.x = std::min<int>(p3_best_size.x, client_size.x - 8);
	p3_best_size.y = std::min<int>(p3_best_size.y, client_size.y - 8);

	this->GetSizer()->SetItemMinSize(m_pP3, p3_best_size);
	this->GetSizer()->Layout();

	wxPoint  client_pos = ClientToScreen(wxPoint(0, 0));
	wxPoint  dg_pos = m_pDG->GetScreenPosition();

	m_pSpacerBNs->AssignSpacer(0, dg_pos.y - client_pos.y);

	this->GetSizer()->Layout();
}

void COCRPanel::RefreshData()
{
	m_pP3->SetBackgroundColour(g_cfg.m_notebook_panels_colour);
}

void COCRPanel::OnBnClickedCreateEmptySub(wxCommandEvent& event)
{
	wxString Str, SubStr, hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
	int i, j, k, sec, msec;
	u64 bt, et, dt, mdt;
	wxString str_int;

	wxDir dir(g_work_dir + "/RGBImages");
	vector<wxString> FileNamesVector;
	vector<u64> BT, ET;
	wxString filename;
	bool res;

	res = dir.GetFirst(&filename);
    while ( res )
    {
		FileNamesVector.push_back(filename);

        res = dir.GetNext(&filename);
    }

	if (FileNamesVector.size() == 0) return;
	
	for (i=0; i<(int)FileNamesVector.size()-1; i++)
	for (j=i+1; j<(int)FileNamesVector.size(); j++)
	{
		if (FileNamesVector[i] > FileNamesVector[j])
		{
			Str = FileNamesVector[i];
			FileNamesVector[i] = FileNamesVector[j];
			FileNamesVector[j] = Str;
		}
	}	

	mdt = (s64)(g_cfg.m_ocr_min_sub_duration * (double)1000);

	for(k=0; k<(int)FileNamesVector.size(); k++)
	{
		Str = FileNamesVector[k];

		hour1 = Str.Mid(0,1);
		min1 = Str.Mid(2,2);
		sec1 = Str.Mid(5,2);
		msec1 = Str.Mid(8,3);

		hour2 = Str.Mid(13,1);
		min2 = Str.Mid(15,2);
		sec2 = Str.Mid(18,2);
		msec2 = Str.Mid(21,3);

		bt = (wxAtoi(hour1)*3600 + wxAtoi(min1)*60 + wxAtoi(sec1))*1000 + wxAtoi(msec1);
		et = (wxAtoi(hour2)*3600 + wxAtoi(min2)*60 + wxAtoi(sec2))*1000 + wxAtoi(msec2);

		BT.push_back(bt);
		ET.push_back(et);
	}

	if (g_join_subs_and_correct_time)
	{
		for (k = 0; k < (int)FileNamesVector.size() - 1; k++)
		{
			if (ET[k] - BT[k] < mdt)
			{
				if (BT[k] + mdt < BT[k + 1])
				{
					ET[k] = BT[k] + mdt;
				}
				else
				{
					ET[k] = BT[k + 1] - 1;
				}
			}
		}
	}	

	wxString srt_sub;
	for(k=0; k<(int)FileNamesVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		Str = VideoTimeToStr2(bt)+
			  " --> "+
			  VideoTimeToStr2(et);

		dt = et - bt;
		sec = (int)(dt/1000);
		msec = (int)(dt%1000);
		
		sec1 = wxString::Format(wxT("%i"), sec);

		str_int = wxString::Format(wxT("%i"), msec);
		if (msec < 10) msec1 = wxT("00") + str_int;
		else
		{
			if (msec < 100) msec1 = wxT("0")+ str_int;
			else msec1 = str_int;
		}

		SubStr = g_DefStringForEmptySub;

		if (g_DefStringForEmptySub.Contains("[sub_duration]"))
		{			
			SubStr.Replace("[sub_duration]", sec1 + "," + msec1);
		}

		srt_sub << (k+1) << wxT("\n") << Str << wxT("\n") << SubStr << "\n\n";
	}

	wxString ass_sub;
	ass_sub << AssSubHead;
	for (k = 0; k < (int)FileNamesVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		dt = et - bt;
		sec = (int)(dt / 1000);
		msec = (int)(dt % 1000);

		sec1 = wxString::Format(wxT("%i"), sec);

		str_int = wxString::Format(wxT("%i"), msec);
		if (msec < 10) msec1 = wxT("00") + str_int;
		else
		{
			if (msec < 100) msec1 = wxT("0") + str_int;
			else msec1 = str_int;
		}

		SubStr = g_DefStringForEmptySub;

		if (g_DefStringForEmptySub.Contains("%sub_duration%"))
		{
			SubStr.Replace("%sub_duration%", sec1 + "," + msec1);
		}

		ass_sub << "Dialogue: 0," + VideoTimeToStr3(bt) + "," + VideoTimeToStr3(et) + ",Default,,0,0,0,," + SubStr + wxT("\n");
	}
	
	SaveSub(srt_sub, ass_sub);
}

void COCRPanel::OnBnClickedCreateSubFromTXTResults(wxCommandEvent& event)
{
	wxString path = wxString(g_work_dir + wxT("/TXTResults/join_txt_results.txt"));

	if (wxFileExists(path))
	{
		CreateSubFromJoinTXTResults(path);
	}
	else
	{
		CreateSubFromTXTResults();
	}
}

void COCRPanel::OnBnClickedCreateSubFromClearedTXTImages(wxCommandEvent& event)
{
	wxString Str, SubStr, Name, hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
	int i, j, k, kb, sec, msec;
	wxString str_int;
	u64 bt, et, dt, mdt;

	wxString dir_path = wxString(g_work_dir + wxT("/TXTImages/"));
	wxDir dir(dir_path);
	vector<wxString> FileNamesVector;
	vector<u64> BT, ET;
	wxString filename;
	bool res;

	res = dir.GetFirst(&filename);
    while ( res )
    {
		FileNamesVector.push_back(filename);

        res = dir.GetNext(&filename);
    }

	if (FileNamesVector.size() == 0) return;

	for (i=0; i<(int)FileNamesVector.size()-1; i++)
	for (j=i+1; j<(int)FileNamesVector.size(); j++)
	{
		if (FileNamesVector[i] > FileNamesVector[j])
		{
			Str = FileNamesVector[i];
			FileNamesVector[i] = FileNamesVector[j];
			FileNamesVector[j] = Str;
		}
	}

	mdt = (s64)(g_cfg.m_ocr_min_sub_duration * (double)1000);

	k = 0;
	while (k < (int)FileNamesVector.size())
	{
		kb = k;
		i = 0;

		if (g_join_subs_and_correct_time)
		{
			while ((k < (int)FileNamesVector.size()) &&
				(FileNamesVector[kb].Mid(0, 11) == FileNamesVector[k].Mid(0, 11))
				)
			{
				k++;
			}
		}
		else
		{
			k++;
		}

		Str = FileNamesVector[kb];

		hour1 = Str.Mid(0,1);
		min1 = Str.Mid(2,2);
		sec1 = Str.Mid(5,2);
		msec1 = Str.Mid(8,3);

		hour2 = Str.Mid(13,1);
		min2 = Str.Mid(15,2);
		sec2 = Str.Mid(18,2);
		msec2 = Str.Mid(21,3);

		bt = (wxAtoi(hour1)*3600 + wxAtoi(min1)*60 + wxAtoi(sec1))*1000 + wxAtoi(msec1);
		et = (wxAtoi(hour2)*3600 + wxAtoi(min2)*60 + wxAtoi(sec2))*1000 + wxAtoi(msec2);

		BT.push_back(bt);
		ET.push_back(et);
	}

	if (g_join_subs_and_correct_time)
	{
		for (k = 0; k < (int)BT.size() - 1; k++)
		{
			if (ET[k] - BT[k] < mdt)
			{
				if (BT[k] + mdt < BT[k + 1])
				{
					ET[k] = BT[k] + mdt;
				}
				else
				{
					ET[k] = BT[k + 1] - 1;
				}
			}
		}
	}

	wxString srt_sub;
	for(k=0; k<(int)BT.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		Str = VideoTimeToStr2(bt)+
			  " --> "+
			  VideoTimeToStr2(et);

		dt = et - bt;
		sec = (int)(dt/1000);
		msec = (int)(dt%1000);
		
		sec1 = wxString::Format(wxT("%i"), sec);

		str_int = wxString::Format(wxT("%i"), msec);
		if (msec < 10) msec1 = wxT("00") + str_int;
		else
		{
			if (msec < 100) msec1 = wxT("0") + str_int;
			else msec1 = str_int;
		}

		SubStr = g_DefStringForEmptySub;

		if (g_DefStringForEmptySub.Contains("%sub_duration%"))
		{			
			SubStr.Replace("%sub_duration%", sec1 + "," + msec1);
		}

		srt_sub << (k+1) << wxT("\n") << Str << wxT("\n") << SubStr << "\n\n";
	}

	wxString ass_sub;
	ass_sub << AssSubHead;
	for (k = 0; k < (int)BT.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		dt = et - bt;
		sec = (int)(dt / 1000);
		msec = (int)(dt % 1000);

		sec1 = wxString::Format(wxT("%i"), sec);

		str_int = wxString::Format(wxT("%i"), msec);
		if (msec < 10) msec1 = wxT("00") + str_int;
		else
		{
			if (msec < 100) msec1 = wxT("0") + str_int;
			else msec1 = str_int;
		}

		SubStr = g_DefStringForEmptySub;

		if (g_DefStringForEmptySub.Contains("%sub_duration%"))
		{
			SubStr.Replace("%sub_duration%", sec1 + "," + msec1);
		}

		ass_sub << "Dialogue: 0," + VideoTimeToStr3(bt) + "," + VideoTimeToStr3(et) + ",Default,,0,0,0,," + SubStr + wxT("\n");
	}

	SaveSub(srt_sub, ass_sub);
}

void COCRPanel::SaveSub(wxString srt_sub, wxString ass_sub)
{
	if (!(m_pMF->m_blnNoGUI))
	{
		wxString sub_name;
		wxString sub_dir;

		if (m_was_sub_save)
		{
			sub_name = GetFileNameWithExtension(m_pMF->m_last_saved_sub_file_path);
			sub_dir = GetFileDir(m_pMF->m_last_saved_sub_file_path);
		}
		else if (m_pMF->m_FileName.size() > 0)
		{
			sub_name = GetFileName(m_pMF->m_FileName) + wxT(".srt");
			sub_dir = GetFileDir(m_pMF->m_FileName);
		}
		else if (m_pMF->m_last_video_file_path.size() > 0)
		{
			sub_name = GetFileName(m_pMF->m_last_video_file_path) + wxT(".srt");
			sub_dir = GetFileDir(m_pMF->m_last_video_file_path);
		}
		else if (m_pMF->m_last_saved_sub_file_path.size() > 0)
		{
			sub_name = GetFileNameWithExtension(m_pMF->m_last_saved_sub_file_path);
			sub_dir = GetFileDir(m_pMF->m_last_saved_sub_file_path);
		}
		else
		{
			sub_name = wxT("sub.srt");
			sub_dir = g_work_dir;
		}

		m_sub_path.Clear();
		wxFileDialog fd(m_pMF, g_cfg.m_file_dialog_title_save_subtitle_as,
			sub_dir, sub_name, g_cfg.m_file_dialog_title_save_subtitle_as_wild_card, wxFD_SAVE);
		int res = fd.ShowModal();

		if (res == wxID_OK)
		{
			m_sub_path = fd.GetPath();
		}
	}
	
	if (m_sub_path.size() > 0)
	{
		wxString ext = GetFileExtension(m_sub_path);

		if (ext == wxT("srt"))
		{
			wxFFileOutputStream ffout(m_sub_path);
			wxTextOutputStream fout(ffout);
			fout << srt_sub;
			fout.Flush();
			ffout.Close();

			m_pMF->m_last_saved_sub_file_path = m_sub_path;
			m_was_sub_save = true;
		}
		else if (ext == wxT("ass"))
		{
			wxFFileOutputStream ffout(m_sub_path);
			wxTextOutputStream fout(ffout);
			fout << ass_sub;
			fout.Flush();
			ffout.Close();

			m_pMF->m_last_saved_sub_file_path = m_sub_path;
			m_was_sub_save = true;
		}
		else
		{
			SaveError("ERROR: SaveSub(): Only .ass and .srt output subtitles formats are supported\n");
		}
	}
}

void COCRPanel::CreateSubFromJoinTXTResults(wxString join_txt_res_path)
{
	wxString Str, SubStr, Name, hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
	int i, j, k, kb, sec, msec, fn;
	wxString str_int;
	wxString filename, img_data_BaseName;
	int img_data_W, img_data_H, img_data_min_x, img_data_min_y, img_data_w, img_data_h, img_data_ln;
	u64 bt, et, dt, mdt;

	wxString dir_txt_images_path = wxString(g_work_dir + wxT("/TXTImages/"));
	wxString dir_rgb_images_path = wxString(g_work_dir + wxT("/RGBImages/"));
	vector<wxString> TXTFileNamesVector, RGBFileNamesVector;
	vector<wxString>* pFileNamesVector;

	if (g_cfg.m_ocr_join_images_join_rgb_images)
	{
		GetFileNames(dir_rgb_images_path, RGBFileNamesVector);
	}

	if (!g_cfg.m_ocr_join_images_join_rgb_images || (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images))
	{
		GetFileNames(dir_txt_images_path, TXTFileNamesVector);
	}

	if (g_cfg.m_ocr_join_images_join_rgb_images)
	{
		pFileNamesVector = &RGBFileNamesVector;
	}
	else
	{
		pFileNamesVector = &TXTFileNamesVector;
	}

	fn = pFileNamesVector->size();

	if (fn == 0)
	{
		return;
	}

	std::map<wxString, int> base_names_map;

	if (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images)
	{
		for (int i = 0; i < (int)TXTFileNamesVector.size(); i++)
		{			

			filename = TXTFileNamesVector[i];
			DecodeImData(GetFileName(filename), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);
			base_names_map[img_data_BaseName] = 1;
		}

		int fi = 0;
		while (fi < (int)RGBFileNamesVector.size())
		{
			filename = RGBFileNamesVector[fi];
			DecodeImData(GetFileName(filename), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);

			if (base_names_map.find(img_data_BaseName) == base_names_map.end())
			{
				for (int i = fi; i < RGBFileNamesVector.size() - 1; i++)
				{
					RGBFileNamesVector[i] = RGBFileNamesVector[i + 1];
				}

				RGBFileNamesVector.pop_back();
				continue;
			}

			fi++;
		}
	}

	fn = pFileNamesVector->size();

	if (fn == 0)
	{
		return;
	}

	vector<u64> BT, ET;
	vector<wxString> TXTVector;	

	vector<wxString> sub_strs(fn);

	{
		wxFileInputStream ffin(join_txt_res_path);
		size_t size = ffin.GetSize();
		custom_buffer<char> data(size + 1);
		ffin.ReadAll(data.m_pData, size);
		data.m_pData[size] = '\0';
		int offset = 0;

		// removing UTF-8 BOM bytes
		if ((data.m_pData[0] == '\xEF') &&
			(data.m_pData[1] == '\xBB') &&
			(data.m_pData[2] == '\xBF'))
		{
			offset = 3;
		}

		wxString str, sub_str;
		wxWCharBuffer buf = wxConvUTF8.cMB2WX(data.m_pData + offset);
		if (buf.length() > 0)
			str = wxString(buf);
		else
			str = wxString(data.m_pData, wxConvLocal);

		str.Replace(wxString(wxT("\r")), wxString(), true);
		str.Trim(true);
		str.Trim(false);
		str += wxString::Format(g_cfg.m_ocr_join_images_sub_id_format, 0);

		for (i = 0; i < fn; i++)
		{
			wxString str_sub_search_by_id = wxString::Format(g_cfg.m_ocr_join_images_sub_search_by_id_format, i + 1);
			wxRegEx re_sub_search_by_id(str_sub_search_by_id);
			
			if (re_sub_search_by_id.Matches(str))
			{
				sub_str = re_sub_search_by_id.GetMatch(str, 1);
				sub_str.Trim(true);
				sub_str.Trim(false);

				if (sub_str.size() == 0)
				{
					sub_str = wxT("OCR_EMPTY_RESULT");
				}

				sub_strs[i] = sub_str;
			}
			else
			{
				sub_strs[i] = wxT("NOT_FOUND");
			}
		}
	}

	mdt = (s64)(g_cfg.m_ocr_min_sub_duration * (double)1000);

	k = 0;
	while (k < fn)
	{
		kb = k;
		i = 0;

		wxString sub_str;

		if (g_join_subs_and_correct_time)
		{
			while ((k < fn) &&
				((*pFileNamesVector)[kb].Mid(0, 11) == (*pFileNamesVector)[k].Mid(0, 11))
				)
			{
				sub_str += sub_strs[k] + wxT("\n");
				k++;
			}
		}
		else
		{
			sub_str += sub_strs[k] + wxT("\n");
			k++;
		}

		sub_str.Trim(true);
		sub_str.Trim(false);

		Str = (*pFileNamesVector)[kb];

		hour1 = Str.Mid(0, 1);
		min1 = Str.Mid(2, 2);
		sec1 = Str.Mid(5, 2);
		msec1 = Str.Mid(8, 3);

		hour2 = Str.Mid(13, 1);
		min2 = Str.Mid(15, 2);
		sec2 = Str.Mid(18, 2);
		msec2 = Str.Mid(21, 3);

		bt = (wxAtoi(hour1) * 3600 + wxAtoi(min1) * 60 + wxAtoi(sec1)) * 1000 + wxAtoi(msec1);
		et = (wxAtoi(hour2) * 3600 + wxAtoi(min2) * 60 + wxAtoi(sec2)) * 1000 + wxAtoi(msec2);

		BT.push_back(bt);
		ET.push_back(et);
		TXTVector.push_back(sub_str);
	}

	k = 0;
	while (k < TXTVector.size())
	{
		if (TXTVector[k].size() == 0)
		{
			if (g_DontDeleteUnrecognizedImages2 == false)
			{
				if (g_join_subs_and_correct_time)
				{
					for (i = k; i < (int)TXTVector.size() - 1; i++)
					{
						BT[i] = BT[i + 1];
						ET[i] = ET[i + 1];
						TXTVector[i] = TXTVector[i + 1];
					}
					BT.pop_back();
					ET.pop_back();
					TXTVector.pop_back();

					continue;
				}
			}
			else
			{
				TXTVector[k] = wxT("#unrecognized text#");
			}
		}

		if ((g_join_subs_and_correct_time) && (k < ((int)TXTVector.size() - 1)))
		{
			if (BT[k + 1] - ET[k] <= 333)
			{
				if (TXTVector[k + 1] == TXTVector[k])
				{
					ET[k] = ET[k + 1];

					for (i = k + 1; i < (int)TXTVector.size() - 1; i++)
					{
						BT[i] = BT[i + 1];
						ET[i] = ET[i + 1];
						TXTVector[i] = TXTVector[i + 1];
					}
					BT.pop_back();
					ET.pop_back();
					TXTVector.pop_back();

					continue;
				}
			}
		}

		k++;
	}

	if (g_join_subs_and_correct_time)
	{
		for (k = 0; k < (int)TXTVector.size() - 1; k++)
		{
			if (ET[k] - BT[k] < mdt)
			{
				if (BT[k] + mdt < BT[k + 1])
				{
					ET[k] = BT[k] + mdt;
				}
				else
				{
					ET[k] = BT[k + 1] - 1;
				}
			}
		}
	}

	wxString srt_sub;
	for (k = 0; k < (int)TXTVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		Str = VideoTimeToStr2(bt) +
			" --> " +
			VideoTimeToStr2(et);

		srt_sub << (k + 1) << wxT("\n") << Str << wxT("\n") << TXTVector[k] << wxT("\n\n");
	}

	wxString ass_sub;
	ass_sub << AssSubHead;
	for (k = 0; k < (int)TXTVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		//example: Dialogue: 0,0:00:03.29,0:00:05.00,Default,,0,0,0,,Regulars gather up!

		wxString txt = TXTVector[k];
		wxRegEx re(wxT("\n"));

		re.ReplaceAll(&txt, wxT("\\\\N"));

		ass_sub << wxT("Dialogue: 0,") + VideoTimeToStr3(bt) + wxT(",") + VideoTimeToStr3(et) + wxT(",Default,,0,0,0,,") + txt + wxT("\n");
	}

	SaveSub(srt_sub, ass_sub);
}

void COCRPanel::CreateSubFromTXTResults()
{
	wxString Str, Name, hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
	int i, j, k, kb, sec, msec, max_mY_dif, max_mI_dif, max_mQ_dif, max_posY_dif;
	int val1, val2, val3, val4, val5, val6, val7, val8;
	wxString fname, image_name;
	u64 bt, et, dt, mdt;
	double max_LH_dif;
	int bln;

	vector<wxString> FileNamesVector;
	vector<wxString> TXTVector;
	vector<u64> BT, ET;	

	wxString dir_path = wxString(g_work_dir + wxT("/TXTResults/"));
	wxDir dir(dir_path);
	wxString filename;
	bool res;

	res = dir.GetFirst(&filename, "*.txt");
    while ( res )
    {
		FileNamesVector.push_back(filename);

        res = dir.GetNext(&filename);
    }

	if (FileNamesVector.size() == 0) return;

	for (i=0; i<(int)FileNamesVector.size()-1; i++)
	for (j=i+1; j<(int)FileNamesVector.size(); j++)
	{
		if (FileNamesVector[i] > FileNamesVector[j])
		{
			Str = FileNamesVector[i];
			FileNamesVector[i] = FileNamesVector[j];
			FileNamesVector[j] = Str;
		}
	}

	mdt = (s64)(g_cfg.m_ocr_min_sub_duration * (double)1000);
	
	int W, H;
    
	k = 0;
	while (k < (int)FileNamesVector.size())
	{
		kb = k;

		Str = FileNamesVector[kb];

		hour1 = Str.Mid(0,1);
		min1 = Str.Mid(2,2);
		sec1 = Str.Mid(5,2);
		msec1 = Str.Mid(8,3);

		hour2 = Str.Mid(13,1);
		min2 = Str.Mid(15,2);
		sec2 = Str.Mid(18,2);
		msec2 = Str.Mid(21,3);

		bt = (wxAtoi(hour1)*3600 + wxAtoi(min1)*60 + wxAtoi(sec1))*1000 + wxAtoi(msec1);
		et = (wxAtoi(hour2)*3600 + wxAtoi(min2)*60 + wxAtoi(sec2))*1000 + wxAtoi(msec2);

		BT.push_back(bt);
		ET.push_back(et);

		Str = "";
		i = 0;
		while( (k < (int)FileNamesVector.size()) &&
			   (FileNamesVector[kb].Mid(0, 11) == FileNamesVector[k].Mid(0, 11))
			 )
		{
			Name = g_work_dir + wxT("/TXTResults/") + FileNamesVector[k];

			wxFileInputStream ffin(Name);
			size_t size = ffin.GetSize();
			custom_buffer<char> data(size+1);
			ffin.ReadAll(data.m_pData, size);
			data.m_pData[size] = '\0';
			int offset = 0;

			// removing UTF-8 BOM bytes
			if ((data.m_pData[0] == '\xEF') &&
				(data.m_pData[1] == '\xBB') &&
				(data.m_pData[2] == '\xBF'))
			{
				offset = 3;
			}			

			wxString str;
			wxWCharBuffer buf = wxConvUTF8.cMB2WX(data.m_pData + offset);
			if (buf.length() > 0)
				str = wxString(buf);
			else
				str = wxString(data.m_pData, wxConvLocal);			

			str.Replace(wxString(wxT("\r")), wxString(), true);
			str.Trim(true);

			if (str.size() > 0)
			{				
				if (i > 0) Str += wxT("\n");
				Str += str;
				i++;
			}

			k++;

			if (!g_join_subs_and_correct_time)
			{
				break;
			}
		}

		TXTVector.push_back(Str);
	}

	k=0;
	while(k < TXTVector.size())
	{
		if (TXTVector[k].size() == 0)
		{
			if (g_DontDeleteUnrecognizedImages2 == false)
			{
				if (g_join_subs_and_correct_time)
				{
					for (i = k; i < (int)TXTVector.size() - 1; i++)
					{
						BT[i] = BT[i + 1];
						ET[i] = ET[i + 1];
						TXTVector[i] = TXTVector[i + 1];
					}
					BT.pop_back();
					ET.pop_back();
					TXTVector.pop_back();

					continue;
				}
			}
			else
			{
				TXTVector[k] = wxT("#unrecognized text#");
			}
		}

		if ((g_join_subs_and_correct_time) && (k < ((int)TXTVector.size() - 1)))
		{
			if (BT[k + 1] - ET[k] <= 333)
			{
				if (TXTVector[k + 1] == TXTVector[k])
				{
					ET[k] = ET[k + 1];

					for (i = k + 1; i < (int)TXTVector.size() - 1; i++)
					{
						BT[i] = BT[i + 1];
						ET[i] = ET[i + 1];
						TXTVector[i] = TXTVector[i + 1];
					}
					BT.pop_back();
					ET.pop_back();
					TXTVector.pop_back();

					continue;
				}
			}
		}

		k++;
	}

	if (g_join_subs_and_correct_time)
	{
		for (k = 0; k < (int)TXTVector.size() - 1; k++)
		{
			if (ET[k] - BT[k] < mdt)
			{
				if (BT[k] + mdt < BT[k + 1])
				{
					ET[k] = BT[k] + mdt;
				}
				else
				{
					ET[k] = BT[k + 1] - 1;
				}
			}
		}
	}

	wxString srt_sub;
	for(k=0; k<(int)TXTVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		Str = VideoTimeToStr2(bt)+
			  " --> "+
			  VideoTimeToStr2(et);

		srt_sub << (k+1) << wxT("\n") << Str << wxT("\n") << TXTVector[k] << wxT("\n\n");
	}

	wxString ass_sub;
	ass_sub << AssSubHead;
	for (k = 0; k < (int)TXTVector.size(); k++)
	{
		bt = BT[k];
		et = ET[k];

		//example: Dialogue: 0,0:00:03.29,0:00:05.00,Default,,0,0,0,,Regulars gather up!

		wxString txt = TXTVector[k];
		wxRegEx re(wxT("\n"));

		re.ReplaceAll(&txt, wxT("\\\\N"));

		ass_sub << wxT("Dialogue: 0,") + VideoTimeToStr3(bt) + wxT(",") + VideoTimeToStr3(et) + wxT(",Default,,0,0,0,,") + txt + wxT("\n");
	}

	SaveSub(srt_sub, ass_sub);
}

void COCRPanel::OnBnClickedJoinTXTImages(wxCommandEvent& event)
{
	const std::lock_guard<std::mutex> lock(m_mutex);

	if (g_IsJoinTXTImages == 0)
	{
		g_IsJoinTXTImages = 1;
		g_RunJoinTXTImages = 1;

		if (!(g_pMF->m_blnNoGUI))
		{
			m_pJOIN->SetLabel(g_cfg.m_ocr_button_join_stop_text);
			this->UpdateSize();

			m_pCCTI->Disable();
			m_pCES->Disable();
			m_pCSCTI->Disable();
			m_pCSTXT->Disable();
			m_pDG->Disable();

			m_pMF->m_pPanel->m_pSSPanel->Disable();
			m_pMF->m_pPanel->m_pSHPanel->Disable();
		}

		m_JoinTXTImagesThread = std::thread(JoinImages);

		if (m_pMF->m_blnNoGUI)
		{
			m_JoinTXTImagesThread.join();
		}
	}
	else
	{
		g_RunJoinTXTImages = 0;
		m_JoinTXTImagesThread.join();
	}
}

void COCRPanel::ThreadJoinTXTImagesThreadEnd(wxCommandEvent& event)
{
	std::unique_lock<std::mutex> lock(m_mutex);
	
	if (m_JoinTXTImagesThread.joinable())
	{
		m_JoinTXTImagesThread.join();
	}

	m_pJOIN->SetLabel(g_cfg.m_ocr_button_join_text);
	this->UpdateSize();

	m_pCCTI->Enable();
	m_pCES->Enable();
	m_pCSCTI->Enable();
	m_pCSTXT->Enable();
	m_pDG->Enable();

	m_pMF->m_pPanel->m_pSSPanel->Enable();
	m_pMF->m_pPanel->m_pSHPanel->Enable();

	g_IsJoinTXTImages = 0;
}

void GetFileNames(wxString dir_path, vector<wxString>& FileNamesVector)
{
	wxDir dir(dir_path);
	wxString file_name;
	wxString Str;
	bool res;

	res = dir.GetFirst(&file_name);
	while (res)
	{
		FileNamesVector.push_back(file_name);

		res = dir.GetNext(&file_name);
	}

	for (int i = 0; i < (int)FileNamesVector.size() - 1; i++)
	{
		for (int j = i + 1; j < (int)FileNamesVector.size(); j++)
		{
			if (FileNamesVector[i] > FileNamesVector[j])
			{
				Str = FileNamesVector[i];
				FileNamesVector[i] = FileNamesVector[j];
				FileNamesVector[j] = Str;
			}
		}
	}
}

void JoinImages()
{
	wxString Str;
	wxString dir_txt_images_path = wxString(g_work_dir + wxT("/TXTImages/"));
	wxString dir_rgb_images_path = wxString(g_work_dir + wxT("/RGBImages/"));
	vector<wxString> TXTFileNamesVector, RGBFileNamesVector;
	vector<wxString> *pFileNamesVector;
	wxString file_name, file_path, dir_path;
	int fn;
	bool res;

	auto end_func = []() {
		if (!(g_pMF->m_blnNoGUI))
		{
			SaveToReportLog("JoinImages: wxPostEvent THREAD_JOIN_TXT_IMAGES_END ...\n");
			wxCommandEvent event(THREAD_JOIN_TXT_IMAGES_END);
			wxPostEvent(g_pMF->m_pPanel->m_pOCRPanel, event);
		}
		else
		{
			g_IsJoinTXTImages = 0;
		}
	};

	if (g_cfg.m_ocr_join_images_clear_dir)
	{
		g_pMF->ClearDir(g_work_dir + "/ImagesJoined");
	}

	if (g_cfg.m_ocr_join_images_join_rgb_images)
	{
		GetFileNames(dir_rgb_images_path, RGBFileNamesVector);		
	}

	if (!g_cfg.m_ocr_join_images_join_rgb_images || (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images))
	{
		GetFileNames(dir_txt_images_path, TXTFileNamesVector);
	}

	if (g_cfg.m_ocr_join_images_join_rgb_images)
	{
		pFileNamesVector = &RGBFileNamesVector;
		dir_path = dir_rgb_images_path;
	}
	else
	{
		pFileNamesVector = &TXTFileNamesVector;
		dir_path = dir_txt_images_path;
	}

	fn = pFileNamesVector->size();

	if (fn == 0)
	{
		end_func();
		return;
	}

	int orig_scale = 1;
	int first_img_data_W, img_data_W, img_data_H, img_data_min_x, img_data_min_y, img_data_w, img_data_h, img_data_ln;
	wxString img_data_BaseName;
	int res_w = 0, res_h = 0, first_w = 0, first_h = 0, dh = 0, dth = 0, average_h = 0;
	int font_size = 0;
	wxFontWeight font_weight = g_cfg.m_ocr_join_images_split_line_font_bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL;

	{
		file_name = (*pFileNamesVector)[0];
		file_path = dir_path + file_name;
		GetImageSize(file_path, first_w, first_h);
		
		if (!DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln))
		{
			SaveError(wxString::Format(wxT("ERROR: JoinImages(): File \"%s\" has wrong img name format\n"), file_name));
			end_func();
			return;
		}

		first_img_data_W = img_data_W;
		orig_scale = first_h / img_data_h;

		res_w = (first_w * g_cfg.m_ocr_join_images_scale) / orig_scale;
	}

	if (!g_cfg.m_ocr_join_images_split_line.empty())
	{
		if (g_cfg.m_ocr_join_images_split_line_font_size == -1)
		{
			if (g_cfg.m_ocr_join_images_join_rgb_images && !g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images)
			{
				SaveError(wxString(wxT("ERROR: JoinImages(): Font size should be manually specified in case of turned off: ")) + g_cfg.m_ocr_label_join_images_use_txt_images_data_for_join_rgb_images + wxString(wxT("\n")));
				end_func();
				return;
			}

			if (!g_cfg.m_ocr_join_images_join_rgb_images || (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images))
			{
				pFileNamesVector = &TXTFileNamesVector;
				dir_path = dir_txt_images_path;
			}
			else
			{
				pFileNamesVector = &RGBFileNamesVector;
				dir_path = dir_rgb_images_path;
			}

			fn = pFileNamesVector->size();

			if (fn == 0)
			{
				end_func();
				return;
			}

			for (int fi = 0; fi < fn; fi++)
			{
				file_name = (*pFileNamesVector)[fi];
				if (!DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln))
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): File \"%s\" has wrong img name format\n"), file_name));
					end_func();
					return;
				}

				if (img_data_W != first_img_data_W)
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): File \"%s\": img_data_w (%d) != first_img_data_W (%d)\n"), file_name, img_data_W, first_img_data_W));
					end_func();
					return;
				}

				average_h += img_data_h / img_data_ln;
			}
			average_h /= fn;

			average_h *= g_cfg.m_ocr_join_images_scale;

			if (average_h < (res_w / 16))
			{
				dh = average_h;
				dth = (dh * 6) / 10;
			}
			else
			{
				dh = res_w / 16;
				dth = (dh * 6) / 10;
			}

			wxMemoryDC dc;
			wxSize text_size;

			do
			{
				font_size++;
				wxFont font(font_size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, font_weight, false, wxEmptyString, wxFONTENCODING_DEFAULT);
				dc.SetFont(font);
				text_size = dc.GetTextExtent(wxT("12345678987654321"));
			} while ((text_size.GetWidth() < res_w) && (text_size.GetHeight() <= dth));
			font_size--;

			if (font_size == 0)
			{
				SaveError(wxT("ERROR: JoinImages(): Unfortunately optimal font size is too small\n"));
				end_func();
				return;
			}
		}
		else
		{
			font_size = g_cfg.m_ocr_join_images_split_line_font_size;

			wxMemoryDC dc;
			wxFont font(font_size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, font_weight, false, wxEmptyString, wxFONTENCODING_DEFAULT);
			dc.SetFont(font);
			wxSize text_size = dc.GetTextExtent(wxT("12345678987654321"));
			dth = text_size.y;
			dh = (dth * 10) / 6;
		}
	}

	struct img_data
	{
		int m_min_y;
		int m_max_y;
	};
	
	std::map<wxString, img_data> txt_imgs_data_map;

	if (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images)
	{
		for (int i = 0; i < (int)TXTFileNamesVector.size(); i++)
		{
			file_name = TXTFileNamesVector[i];
			DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);

			if (txt_imgs_data_map.find(img_data_BaseName) == txt_imgs_data_map.end())
			{
				txt_imgs_data_map[img_data_BaseName].m_min_y = img_data_min_y;
				txt_imgs_data_map[img_data_BaseName].m_max_y = img_data_min_y + img_data_h - 1;
			}
			else
			{
				if (img_data_min_y < txt_imgs_data_map[img_data_BaseName].m_min_y)
				{
					txt_imgs_data_map[img_data_BaseName].m_min_y = img_data_min_y;
				}

				if (img_data_min_y + img_data_h - 1 > txt_imgs_data_map[img_data_BaseName].m_max_y)
				{
					txt_imgs_data_map[img_data_BaseName].m_max_y = img_data_min_y + img_data_h - 1;
				}
			}
		}
		
		int fi = 0;
		while (fi < (int)RGBFileNamesVector.size())
		{
			file_name = RGBFileNamesVector[fi];
			DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);

			if (txt_imgs_data_map.find(img_data_BaseName) == txt_imgs_data_map.end())
			{
				for (int i = fi; i < RGBFileNamesVector.size() - 1; i++)
				{
					RGBFileNamesVector[i] = RGBFileNamesVector[i + 1];					
				}
				
				RGBFileNamesVector.pop_back();
				continue;
			}

			fi++;
		}
	}

	if (g_cfg.m_ocr_join_images_join_rgb_images)
	{
		pFileNamesVector = &RGBFileNamesVector;
		dir_path = dir_rgb_images_path;
	}
	else
	{
		pFileNamesVector = &TXTFileNamesVector;
		dir_path = dir_txt_images_path;
	}

	fn = pFileNamesVector->size();

	if (fn == 0)
	{
		end_func();
		return;
	}

	int fi = 0;
	while (fi < fn)
	{
		int fi_start = fi;
		int fi_end = fn - 1;
		int cur_w, cur_h, save_h, res_h = 0;

		fi = fi_start;
		while (fi < fn)
		{
			file_name = (*pFileNamesVector)[fi];
			DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);

			if (g_cfg.m_ocr_join_images_join_rgb_images && g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images)
			{
				save_h = (txt_imgs_data_map[img_data_BaseName].m_max_y - txt_imgs_data_map[img_data_BaseName].m_min_y + 1) * g_cfg.m_ocr_join_images_scale;
			}
			else
			{
				save_h = img_data_h * g_cfg.m_ocr_join_images_scale;
			}

			if (res_h + dh + save_h > 65500)
			{
				fi_end = fi - 1;
				break;
			}

			res_h += dh + save_h;

			if ((fi - fi_start + 1) == g_cfg.m_ocr_join_images_max_number)
			{
				fi_end = fi;
				break;
			}

			fi++;
		}

		wxFont font(font_size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, font_weight, false, wxEmptyString, wxFONTENCODING_DEFAULT);		

		simple_buffer<u8> ImRes(res_w * res_h * (g_cfg.m_ocr_join_images_join_rgb_images ? 3 : 1), (u8)255);

		int h_ofset = 0;
		for (fi = fi_start; fi <= fi_end; fi++)
		{
			if (g_RunJoinTXTImages == 0)
			{
				end_func();
				return;
			}

			file_name = (*pFileNamesVector)[fi];
			file_path = dir_path + file_name;

			if (!g_cfg.m_ocr_join_images_split_line.empty())
			{
				wxBitmap bitmap(res_w, dh);
				wxMemoryDC dc;
				dc.SelectObject(bitmap);
				dc.SetFont(font);
				wxSize text_size;

				wxString hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
				u64 bt, et;

				Str = file_name;

				hour1 = Str.Mid(0, 1);
				min1 = Str.Mid(2, 2);
				sec1 = Str.Mid(5, 2);
				msec1 = Str.Mid(8, 3);

				hour2 = Str.Mid(13, 1);
				min2 = Str.Mid(15, 2);
				sec2 = Str.Mid(18, 2);
				msec2 = Str.Mid(21, 3);

				bt = (wxAtoi(hour1) * 3600 + wxAtoi(min1) * 60 + wxAtoi(sec1)) * 1000 + wxAtoi(msec1);
				et = (wxAtoi(hour2) * 3600 + wxAtoi(min2) * 60 + wxAtoi(sec2)) * 1000 + wxAtoi(msec2);				

				Str = g_cfg.m_ocr_join_images_split_line;

				wxRegEx re_bt(wxT("\\[begin_time\\]"));
				wxString str_bt = VideoTimeToStr2(bt);
				if (re_bt.Matches(Str))
				{
					re_bt.Replace(&Str, str_bt);
				}

				wxRegEx re_et(wxT("\\[end_time\\]"));
				wxString str_et = VideoTimeToStr2(et);
				if (re_et.Matches(Str))
				{
					re_et.Replace(&Str, str_et);
				}

				wxRegEx re_sub_id(wxT("\\[sub_id\\]"));
				wxString str_sub_id = wxString::Format(g_cfg.m_ocr_join_images_sub_id_format, fi + 1);
				if (re_sub_id.Matches(Str))
				{
					re_sub_id.Replace(&Str, str_sub_id);
				}

				text_size = dc.GetTextExtent(Str);
				int text_x = (res_w - text_size.GetWidth()) / 2;
				int text_y = (dh - text_size.GetHeight()) / 2;

				wxBrush brush(wxColour(255, 255, 255));
				wxPen pen(wxColour(255, 255, 255));
				dc.SetBrush(brush);
				dc.SetPen(pen);
				dc.DrawRectangle(0, 0, res_w, dh);

				SaveToReportLog(wxString::Format(wxT("JoinImages: DrawText(%s, %d, %d) res_w(%d) dh(%d)...\n"), Str, text_x, text_y, res_w, dh));				
				dc.SetTextForeground(wxColour(0, 0, 0));
				dc.DrawText(Str, text_x, text_y);

				wxImage img = bitmap.ConvertToImage();
				u8* img_data = img.GetData();

				if (g_cfg.m_ocr_join_images_join_rgb_images)
				{
					for (int y = 0; y < img.GetHeight(); y++)
					{
						for (int x = 0; x < img.GetWidth(); x++, img_data += 3)
						{
							if ((img_data[0] != 255) || (img_data[1] != 255) || (img_data[2] != 255))
							{
								int offset = (((h_ofset + y) * res_w) + x) * 3;
								ImRes[offset] = 0;
								ImRes[offset + 1] = 0;
								ImRes[offset + 2] = 0;
							}
						}
					}
				}
				else
				{
					for (int y = 0; y < img.GetHeight(); y++)
					{
						for (int x = 0; x < img.GetWidth(); x++, img_data += 3)
						{
							if ((img_data[0] != 255) || (img_data[1] != 255) || (img_data[2] != 255))
							{
								ImRes[((h_ofset + y) * res_w) + x] = 0;
							}
						}
					}
				}
			}

			h_ofset += dh;

			if (g_cfg.m_ocr_join_images_join_rgb_images)
			{
				int cur_w, cur_h;
				simple_buffer<u8> Img;
				LoadBGRImage(Img, file_path, cur_w, cur_h, true);

				if (res_w != (cur_w * g_cfg.m_ocr_join_images_scale) / orig_scale)
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): res_w (%d) != (cur_w (%d) * g_cfg.m_ocr_join_images_scale (%d)) / orig_scale (%d)\n"), file_path, res_w, cur_w, g_cfg.m_ocr_join_images_scale, orig_scale));
					end_func();
					return;
				}

				DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln, &img_data_BaseName);
				if (img_data_h != cur_h / orig_scale)
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): img_data_h (%d) != cur_h (%d) / orig_scale (%d)\n"), file_path, img_data_h, cur_h, orig_scale));
					end_func();
					return;
				}

				int yb;

				if (g_cfg.m_ocr_join_images_use_txt_images_data_for_join_rgb_images)
				{
					save_h = (txt_imgs_data_map[img_data_BaseName].m_max_y - txt_imgs_data_map[img_data_BaseName].m_min_y + 1) * g_cfg.m_ocr_join_images_scale;
					yb = (txt_imgs_data_map[img_data_BaseName].m_min_y - img_data_min_y) * g_cfg.m_ocr_join_images_scale;
				}
				else
				{
					save_h = img_data_h * g_cfg.m_ocr_join_images_scale;
					yb = 0;
				}

				if (g_cfg.m_ocr_join_images_scale != orig_scale)
				{
					cv::Mat cv_Im, cv_ImRes;
					BGRImageToMat(Img, cur_w, cur_h, cv_Im);
					cv::resize(cv_Im, cv_ImRes, cv::Size(0, 0),
						(double)g_cfg.m_ocr_join_images_scale / (double)orig_scale,
						(double)g_cfg.m_ocr_join_images_scale / (double)orig_scale);

					simple_buffer<u8> ImgRes(cv_ImRes.cols * cv_ImRes.rows * 3);
					BGRMatToImage(cv_ImRes, cv_ImRes.cols, cv_ImRes.rows, ImgRes);

					if (cv_ImRes.cols != res_w)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cv_ImRes.cols (%d) != res_w (%d)\n"), file_path, cv_ImRes.cols, res_w));
						end_func();
						return;
					}

					if (cv_ImRes.rows < yb + save_h)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cv_ImRes.rows (%d) < yb (%d) + save_h (%d)\n"), file_path, cv_ImRes.rows, yb, save_h));
						end_func();
						return;
					}

					ImRes.copy_data(ImgRes, res_w * h_ofset * 3, res_w * yb * 3, res_w * save_h * 3);
				}
				else
				{
					if (cur_w != res_w)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cur_w (%d) != res_w (%d)\n"), file_path, cur_w, res_w));
						end_func();
						return;
					}

					if (cur_h < yb + save_h)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cur_h (%d) < yb (%d) + save_h (%d)\n"), file_path, cur_h, yb, save_h));
						end_func();
						return;
					}

					ImRes.copy_data(Img, res_w * h_ofset * 3, res_w * yb * 3, res_w * save_h * 3);
				}
			}
			else
			{			
				int cur_w, cur_h;
				simple_buffer<u8> Img;
				LoadBinaryImage(Img, file_path, cur_w, cur_h, (u8)255, true);
				
				if (res_w != (cur_w * g_cfg.m_ocr_join_images_scale) / orig_scale)
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): res_w (%d) != (cur_w (%d) * g_cfg.m_ocr_join_images_scale (%d)) / orig_scale (%d)\n"), file_path, res_w, cur_w, g_cfg.m_ocr_join_images_scale, orig_scale));
					end_func();
					return;
				}

				DecodeImData(GetFileName(file_name), &img_data_W, &img_data_H, &img_data_min_x, &img_data_min_y, &img_data_w, &img_data_h, &img_data_ln);
				if (img_data_h != cur_h / orig_scale)
				{
					SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): img_data_h (%d) != cur_h (%d) / orig_scale (%d)\n"), file_path, img_data_h, cur_h, orig_scale));
					end_func();
					return;
				}

				save_h = img_data_h * g_cfg.m_ocr_join_images_scale;

				if (g_cfg.m_ocr_join_images_scale != orig_scale)
				{
					cv::Mat cv_Im, cv_ImRes;
					GreyscaleImageToMat(Img, cur_w, cur_h, cv_Im);
					cv::resize(cv_Im, cv_ImRes, cv::Size(0, 0),
						(double)g_cfg.m_ocr_join_images_scale / (double)orig_scale,
						(double)g_cfg.m_ocr_join_images_scale / (double)orig_scale);

					simple_buffer<u8> ImgRes(cv_ImRes.cols * cv_ImRes.rows);
					BinaryMatToImage(cv_ImRes, cv_ImRes.cols, cv_ImRes.rows, ImgRes, (u8)255);

					if (cv_ImRes.cols != res_w)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cv_ImRes.cols (%d) != res_w (%d)\n"), file_path, cv_ImRes.cols, res_w));
						end_func();
						return;
					}

					if (cv_ImRes.rows < save_h)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cv_ImRes.rows (%d) < save_h (%d)\n"), file_path, cv_ImRes.rows, save_h));
						end_func();
						return;
					}

					ImRes.copy_data(ImgRes, res_w * h_ofset, 0, res_w * save_h);
				}
				else
				{
					if (cur_w != res_w)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cur_w (%d) != res_w (%d)\n"), file_path, cur_w, res_w));
						end_func();
						return;
					}

					if (cur_h < save_h)
					{
						SaveError(wxString::Format(wxT("ERROR: JoinImages(): file (%s): cur_h (%d) < save_h (%d)\n"), file_path, cur_h, save_h));
						end_func();
						return;
					}

					ImRes.copy_data(Img, res_w* h_ofset, 0, res_w * save_h);
				}
			}
			
			h_ofset += save_h;
		}

		Str = wxString::Format(wxT("%s%s%s"), wxT("/ImagesJoined/"), GetFileName((*pFileNamesVector)[fi_start]), g_im_save_format);

		if (g_cfg.m_ocr_join_images_join_rgb_images)
		{
			SaveBGRImage(ImRes, Str, res_w, res_h);
		}
		else
		{
			SaveGreyscaleImage(ImRes, Str, res_w, res_h);
		}

		fi = fi_end + 1;
	}

	wxString path = wxString(g_work_dir + wxT("/TXTResults/join_txt_results.txt"));

	if (!wxFileExists(path))
	{
		wxFFileOutputStream ffout(path, wxT("wb"));
		ffout.Close();
	}

	end_func();
}

void FindTextLinesWithExcFilter(FindTextLinesRes *res, simple_buffer<u8>* pImF, simple_buffer<u8>* pImNF, simple_buffer<u8>* pImNE, simple_buffer<u8>* pImIL)
{
	try
	{
		res->m_res = FindTextLines(res->m_ImBGR, res->m_ImClearedTextScaled, *pImF, *pImNF, *pImNE, *pImIL, res->m_SaveDir, res->m_BaseImgName, res->m_w, res->m_h, res->m_W, res->m_H, res->m_xmin, res->m_ymin);
	}
	catch (const exception& e)
	{
		SaveError(wxT("Got C++ Exception: got error in FindTextLinesWithExcFilter:FindTextLines()") + wxString(e.what()) + wxT("\n"));
		res->m_res = -1;
	}
}

void FindTextLines(wxString FileName, FindTextLinesRes& res)
{
	try
	{
		wxString Str, BaseImgName;
		int w, h, W, H, w2, h2, xmin, xmax, ymin, ymax;

		GetImageSize(FileName, w, h);
		GetImInfo(GetFileName(FileName), w, h, &W, &H, &xmin, &xmax, &ymin, &ymax, &BaseImgName);

		res.m_w = w;
		res.m_h = h;
		res.m_W = W;
		res.m_H = H;
		res.m_xmin = xmin;
		res.m_ymin = ymin;
		res.m_xmax = xmax;
		res.m_ymax = ymax;
		res.m_ImBGR = simple_buffer<u8>(w * h * 3, (u8)0);
		res.m_ImClearedTextScaled = simple_buffer<u8>(w * g_scale * h * g_scale, (u8)0);

		LoadBGRImage(res.m_ImBGR, FileName);

		simple_buffer<u8> ImFF(w * h)/*3*/, ImTF(w * h)/*5*/, ImNE(w * h)/*1*/, ImIL/*0*/;

		{
			simple_buffer<u8> ImSF(w * h)/*4*/, ImY(w * h)/*2*/;			
			res.m_res = GetTransformedImage(res.m_ImBGR, ImFF, ImSF, ImTF, ImNE, ImY, w, h, W, H, 0, w - 1);
			if (res.m_pImFF != NULL) res.m_pImFF->copy_data(ImFF, ImFF.m_size);
			if (res.m_pImSF != NULL) res.m_pImSF->copy_data(ImSF, ImSF.m_size);
			if (res.m_pImTF != NULL) res.m_pImTF->copy_data(ImTF, ImTF.m_size);
			if (res.m_pImNE != NULL) res.m_pImNE->copy_data(ImNE, ImNE.m_size);
			if (res.m_pImY != NULL) res.m_pImY->copy_data(ImY, ImY.m_size);

			if (g_show_results)
			{
				SaveBGRImage(res.m_ImBGR, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImBGR" + g_im_save_format, w, h);
				SaveGreyscaleImage(ImFF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImFF" + g_im_save_format, w, h);
				SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImTF" + g_im_save_format, w, h);
			}
		}		

		if (g_show_transformed_images_only)
		{
			Str = FileName;
			Str = GetFileName(Str);
			Str = g_work_dir + "/TXTImages/" + Str + g_im_save_format;
			SaveGreyscaleImage(ImTF, wxString(Str), w, h);
			res.m_ImClearedTextScaled = ImTF;
			return;
		}

		// Test button pressed
		if ((!g_generate_cleared_text_images_on_test) && (res.m_pImFF != NULL))
		{
			return;
		}

		if (g_use_ISA_images_for_get_txt_area)
		{
			Str = g_work_dir + "/ISAImages/" + GetFileName(FileName) + g_im_save_format;

			if (wxFileExists(Str))
			{
				GetImageSize(Str, w2, h2);

				if ( (h2 == ((h*w2)/w)) && (h2 <= h) )
				{
					simple_buffer<u8> ImTFOrig(ImTF);
					LoadBinaryImage(ImTF, Str, w2, h2);
					if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImage" + g_im_save_format, w2, h2);

					if (h2 != h)
					{
						cv::Mat cv_ImGROrig, cv_ImGR;
						GreyscaleImageToMat(ImTF, w2, h2, cv_ImGROrig);
						cv::resize(cv_ImGROrig, cv_ImGR, cv::Size(0, 0), (double)w/w2, (double)h/h2);
						BinaryMatToImage(cv_ImGR, w, h, ImTF, (u8)255);
						if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImageScaled" + g_im_save_format, w, h);
					}

					if (g_show_results) SaveGreyscaleImage(ImTFOrig, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImTFOrig" + g_im_save_format, w, h);
					CombineTwoImages(ImTFOrig, ImTF, w, h);
					if (g_show_results) SaveGreyscaleImage(ImTFOrig, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImTFOrigCombinedWithISAImage" + g_im_save_format, w, h);

					RestoreStillExistLines(ImTF, ImTFOrig, w, h, W, H);
					if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImageRestoredStillExistLinesByImTFOrig" + g_im_save_format, w, h);

					if (g_show_results) SaveGreyscaleImage(ImFF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImFF" + g_im_save_format, w, h);
					CombineTwoImages(ImFF, ImTF, w, h);
					if (g_show_results) SaveGreyscaleImage(ImFF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImFFCombinedWithISAImage" + g_im_save_format, w, h);

					ExtendImFWithDataFromImNF(ImTF, ImFF, w, h);
					if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImageExtImFF" + g_im_save_format, w, h);
				}
				else
				{
					g_pMF->ShowErrorMessage(wxString::Format(wxT("ISA Image \"%s\" has wrong size"), GetFileName(FileName) + g_im_save_format));

					if (g_RunCreateClearedTextImages)
					{						
						g_RunCreateClearedTextImages = 0;
					}
					return;
				}
			}
		}

		// IL image	
		if (g_use_ILA_images_for_get_txt_area)
		{
			Str = g_work_dir + "/ILAImages/" + GetFileName(FileName) + g_im_save_format;

			if (wxFileExists(Str))
			{
				GetImageSize(Str, w2, h2);
				
				if ((h2 == ((h * w2) / w)) && (h2 <= h))
				{
					ImIL.set_size(w * h);
					LoadBinaryImage(ImIL, wxString(Str), w2, h2);
					if (g_show_results) SaveGreyscaleImage(ImIL, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ILAImage" + g_im_save_format, w2, h2);

					if (h2 != h)
					{
						cv::Mat cv_ImGROrig, cv_ImGR;
						GreyscaleImageToMat(ImIL, w2, h2, cv_ImGROrig);
						cv::resize(cv_ImGROrig, cv_ImGR, cv::Size(0, 0), (double)w / w2, (double)h / h2);
						BinaryMatToImage(cv_ImGR, w, h, ImIL, (u8)255);
						if (g_show_results) SaveGreyscaleImage(ImIL, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ILAImageScaled" + g_im_save_format, w, h);
					}

					if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImage" + g_im_save_format, w, h);
					IntersectTwoImages(ImTF, ImIL, w, h);
					if (g_show_results) SaveGreyscaleImage(ImTF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ISAImageIntILAImage" + g_im_save_format, w, h);

					if (g_show_results) SaveGreyscaleImage(ImFF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImFF" + g_im_save_format, w, h);
					IntersectTwoImages(ImFF, ImIL, w, h);
					if (g_show_results) SaveGreyscaleImage(ImFF, "/DebugImages/OCRPanel_FindTextLines_line" + wxString::Format(wxT("%i"), __LINE__) + "_ImFFIntILAImage" + g_im_save_format, w, h);
				}
				else
				{
					g_pMF->ShowErrorMessage(wxString::Format(wxT("ILA Image \"%s\" has wrong size"), GetFileName(FileName) + g_im_save_format));
					if (g_RunCreateClearedTextImages)
					{
						g_RunCreateClearedTextImages = 0;
					}
					return;
				}
			}			
		}		

		res.m_BaseImgName = BaseImgName;

		FindTextLinesWithExcFilter(&res, &ImTF, &ImFF, &ImNE, &ImIL);

		if (res.m_res == -1)
		{
			SaveError(wxT("Got C Exception during FindTextLinesWithExcFilter on FileName: ") + FileName + wxT("\n"));
		}

		// free memory for reduce usage
		if (g_pMF->m_blnNoGUI)
		{
			res.m_ImBGR.set_size(0);
			res.m_ImClearedTextScaled.set_size(0);
		}
	}
	catch (const exception& e)
	{
		SaveError(wxT("Got C++ Exception: got error in FindTextLines: ") + wxString(e.what()) + wxT("\n"));
	}
}

struct find_text_queue_data
{
	wxString m_file_name;
	FindTextLinesRes* m_p_res;
	my_event* m_p_event;
	bool m_is_end = false;
};

shared_custom_task TaskFindTextLines(threadsafe_queue<find_text_queue_data> &task_queue)
{
	return shared_custom_task([&task_queue] {
			find_text_queue_data text_queue_data;

			while (1)
			{
				task_queue.wait_and_pop(text_queue_data);
				
				if (text_queue_data.m_is_end)
				{
					break;
				}
				else
				{
					custom_set_started(text_queue_data.m_p_event);

					if (g_RunCreateClearedTextImages == 0)
					{
						text_queue_data.m_p_event->m_need_to_skip = true;
					}
					else
					{
						FindTextLines(g_work_dir + "/RGBImages/" + text_queue_data.m_file_name, *text_queue_data.m_p_res);															
					}

					text_queue_data.m_p_event->set();
				}
			}
		}
	);
}

#ifdef WIN32
s64 getTotalSystemMemory()
{
	MEMORYSTATUSEX status;
	status.dwLength = sizeof(status);
	GlobalMemoryStatusEx(&status);
	
	// ullTotalPhys
	// The amount of actual physical memory, in bytes.
	// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
	return status.ullTotalPhys;
}
#endif

void COCRPanel::OnBnClickedCreateClearedTextImages(wxCommandEvent& event)
{
	std::unique_lock<std::mutex> lock(m_mutex);

	if (g_IsCreateClearedTextImages == 0)
	{
		wxDir dir(g_work_dir + "/RGBImages");
		vector<wxString> FileNamesVector;
		wxString filename;
		wxString Str;
		bool bres;

		bres = dir.GetFirst(&filename);
		while (bres)
		{
			FileNamesVector.push_back(filename);

			bres = dir.GetNext(&filename);
		}

		for (int i = 0; i < (int)FileNamesVector.size() - 1; i++)
			for (int j = i + 1; j < (int)FileNamesVector.size(); j++)
			{
				if (FileNamesVector[i] > FileNamesVector[j])
				{
					Str = FileNamesVector[i];
					FileNamesVector[i] = FileNamesVector[j];
					FileNamesVector[j] = Str;
				}
			}

		int NImages = FileNamesVector.size();

		if (NImages > 0)
		{
			g_IsCreateClearedTextImages = 1;
			g_RunCreateClearedTextImages = 1;

			if (g_clear_txt_folders)
			{
				m_pMF->ClearDir(g_work_dir + "/TXTImages");
				m_pMF->ClearDir(g_work_dir + "/TXTResults");
				m_pMF->ClearDir(g_work_dir + "/ImagesJoined");
			}

			if (!(m_pMF->m_blnNoGUI))
			{
				m_pCCTI->SetLabel(g_cfg.m_ocr_button_ccti_stop_text);
				this->UpdateSize();

				m_pCES->Disable();
				m_pJOIN->Disable();
				m_pCSCTI->Disable();
				m_pCSTXT->Disable();
				m_pDG->Disable();

				if (m_pMF->m_VIsOpen)
				{
					wxCommandEvent event;
					m_pMF->OnStop(event);

					m_pMF->m_VIsOpen = false;

					if (m_pMF->m_timer.IsRunning())
					{
						m_pMF->m_timer.Stop();
					}

					m_pMF->m_ct = -1;

					m_pMF->m_pVideoBox->m_pButtonPause->Disable();
					m_pMF->m_pVideoBox->m_pButtonRun->Disable();
					m_pMF->m_pVideoBox->m_pButtonStop->Disable();
					m_pMF->m_pImageBox->ClearScreen();
					m_pMF->m_pVideo->SetNullRender();
				}

				m_pMF->m_pVideoBox->m_plblVB->SetLabel(g_cfg.m_video_box_title);
				g_cfg.m_video_box_lblTIME_label = wxT("");
				m_pMF->m_pVideoBox->m_plblTIME->SetLabel(g_cfg.m_video_box_lblTIME_label);

				m_pMF->m_pPanel->m_pSSPanel->Disable();
				m_pMF->m_pPanel->m_pSHPanel->Disable();

				m_pMF->m_pVideoBox->m_pSB->Enable(true);
				m_pMF->m_pVideoBox->m_pSB->SetScrollPos(0);
				m_pMF->m_pVideoBox->m_pSB->SetScrollRange(0, NImages);

				if (m_pMF->m_pVideoBox->m_pImage != NULL)
				{
					delete m_pMF->m_pVideoBox->m_pImage;
					m_pMF->m_pVideoBox->m_pImage = NULL;
				}
				m_pMF->m_pVideoBox->ClearScreen();

				if (m_pMF->m_pImageBox->m_pImage != NULL)
				{
					delete m_pMF->m_pImageBox->m_pImage;
					m_pMF->m_pImageBox->m_pImage = NULL;
				}
				m_pMF->m_pImageBox->ClearScreen();

				wxString str;
				str.Printf(g_cfg.m_ccti_start_progress_format_string + wxT("   "), NImages);
				g_cfg.m_video_box_lblTIME_label = str;
				m_pMF->m_pVideoBox->m_plblTIME->SetLabel(g_cfg.m_video_box_lblTIME_label);
			}

			m_FileNamesVector = FileNamesVector;
			m_SaveDir = wxString(wxT("/TXTImages/"));
			
			m_CCTIThread = std::thread(CreateClearedTextImages);

			if (m_pMF->m_blnNoGUI)
			{
				m_CCTIThread.join();
			}
		}
	}
	else
	{
		g_RunCreateClearedTextImages = 0;
		m_CCTIThread.join();
	}
}

struct ProgressData
{
	wxString m_ProgressStr;
	wxString m_VBLabel;
	int m_SBpos;
};

void COCRPanel::OnUpdateCCTIProgress(wxThreadEvent& event)
{
	const std::lock_guard<std::mutex> lock(m_mutex);
	ProgressData pd = event.GetPayload<ProgressData>();	

	g_cfg.m_video_box_lblTIME_label = pd.m_ProgressStr;
	m_pMF->m_pVideoBox->m_plblTIME->SetLabel(g_cfg.m_video_box_lblTIME_label);
	g_cfg.m_video_box_lblVB_title = pd.m_VBLabel;
	m_pMF->m_pVideoBox->m_plblVB->SetLabel(g_cfg.m_video_box_lblVB_title);
	m_pMF->m_pVideoBox->m_pSB->SetScrollPos(pd.m_SBpos);
}

void CreateClearedTextImages()
{
	vector<wxString> FileNamesVector = g_pMF->m_pPanel->m_pOCRPanel->m_FileNamesVector;
	wxString SaveDir = g_pMF->m_pPanel->m_pOCRPanel->m_SaveDir;
	
	g_IsCreateClearedTextImages = 1;
	
	g_color_ranges = GetColorRanges(g_use_filter_color);
	g_outline_color_ranges = GetColorRanges(g_use_outline_filter_color);

	g_text_alignment = ConvertStringToTextAlignment(g_text_alignment_string);

	if (g_ocr_threads <= 0)
	{
		g_ocr_threads = std::thread::hardware_concurrency();

#ifdef WIN64
		double max_mem = (double)getTotalSystemMemory()/(1 << 30);
		double one_thr_max_mem = (double)13/16; // MAX ~10.7Gb in 16 thread
		int max_thrs = std::max<int>((int)(max_mem / one_thr_max_mem), 1);
		if (g_ocr_threads > max_thrs)
		{
			g_ocr_threads = max_thrs;
		}
#endif
#ifdef WINX86
		if (g_ocr_threads > 1)
		{
			g_ocr_threads = 1;
		}
#endif
	}


	wxString Str, dStr;
	wxString fname;
	ofstream fout;
	char str[30];
	int i, j, k, xmin, xmax, ymin, ymax, val;
	
	int w1, h1, w2, h2, YB1, YB2, bln;
	wxString hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
	u64 bt1, et1, bt2, et2;

	int res;	

	//vector<wxString> prevSavedFiles;
	vector<u64> BT, ET;	
	s64 t1, dt, num_calls;

	//t1 = GetTickCount();	

	if (g_clear_test_images_folder) g_pMF->ClearDir(g_work_dir + "/DebugImages");
		
	int NImages = FileNamesVector.size();

	if (NImages > 0)
	{
		threadsafe_queue<find_text_queue_data> task_queue;
		simple_buffer<FindTextLinesRes*> task_results(NImages);
		simple_buffer<my_event*> task_events(NImages);
		vector<shared_custom_task> tasks(g_ocr_threads, shared_custom_task([] {}));
		wait_all(begin(tasks), end(tasks));		

		for (k = 0; k < NImages; k++)
		{
			task_results[k] = new FindTextLinesRes(SaveDir);
			task_events[k] = new my_event();
			find_text_queue_data queue_data{ FileNamesVector[k], task_results[k], task_events[k] };
			task_queue.push(queue_data);
		}

		for (k = 0; k < g_ocr_threads; k++)
		{
			find_text_queue_data queue_data{ "", NULL, NULL, true };
			task_queue.push(queue_data);
			tasks[k] = TaskFindTextLines(task_queue);
		}

		std::chrono::time_point<std::chrono::high_resolution_clock> start_time = std::chrono::high_resolution_clock::now();

		for (k = 0; k < NImages; k++)
		{
			try
			{
				task_events[k]->wait();

				if (task_events[k]->m_need_to_skip)
				{
					delete task_events[k];
					delete task_results[k];
					continue;
				}

				FindTextLinesRes* p_task_res = task_results[k];

				res = p_task_res->m_res;

				if (res == -1)
				{
					SaveError(wxT("Got C Exception during FindTextLinesWithExcFilter on FileName: ") + FileNamesVector[k] + wxT("\n"));
				}

				if (!(g_pMF->m_blnNoGUI))
				{
					{
						simple_buffer<u8> ImTMP_BGR(p_task_res->m_W * p_task_res->m_H * 3);
						ImBGRToNativeSize(p_task_res->m_ImBGR, ImTMP_BGR, p_task_res->m_w, p_task_res->m_h, p_task_res->m_W, p_task_res->m_H, p_task_res->m_xmin, p_task_res->m_xmax, p_task_res->m_ymin, p_task_res->m_ymax);
						g_pViewBGRImage[0](ImTMP_BGR, p_task_res->m_W, p_task_res->m_H);
					}

					{
						simple_buffer<u8> ImTMP_ClearedTextScaled(p_task_res->m_W * g_scale * p_task_res->m_H * g_scale);
						ImToNativeSize(p_task_res->m_ImClearedTextScaled, ImTMP_ClearedTextScaled, p_task_res->m_w * g_scale, p_task_res->m_h * g_scale, p_task_res->m_W * g_scale, p_task_res->m_H * g_scale, p_task_res->m_xmin * g_scale, p_task_res->m_xmax * g_scale, p_task_res->m_ymin * g_scale, p_task_res->m_ymax * g_scale);
						g_pViewGreyscaleImage[1](ImTMP_ClearedTextScaled, p_task_res->m_W * g_scale, p_task_res->m_H * g_scale);
					}

					std::chrono::time_point<std::chrono::high_resolution_clock> cur_time = std::chrono::high_resolution_clock::now();
					double progress = ((double)(k + 1) / (double)NImages) * 100.0;

					u64 run_time = std::chrono::duration_cast<std::chrono::milliseconds>(cur_time - start_time).count();
					u64 eta = (u64)((double)run_time * (100.0 - progress) / progress);

					ProgressData pd;

					pd.m_ProgressStr.Printf(g_cfg.m_ccti_progress_format_string + wxT("   "), progress, g_pMF->ConvertTime(eta), g_pMF->ConvertTime(run_time), k + 1, NImages);
					
					Str = FileNamesVector[k];
					Str = GetFileName(Str);
					pd.m_VBLabel = wxString(g_cfg.m_video_box_title + wxT(" \"")) + Str + wxT("\"");

					pd.m_SBpos = k + 1;

					auto event = new wxThreadEvent(UPDATE_CCTI_PROGRESS);
					event->SetPayload(pd);
					wxQueueEvent(g_pMF->m_pPanel->m_pOCRPanel, event);
				}
				

				if ((res == 0) && (g_DontDeleteUnrecognizedImages1 == true))
				{
					Str = FileNamesVector[k];
					Str = GetFileName(Str);
					Str = wxT("/TXTImages/") + Str + wxT("_00001") + g_im_save_format;

					simple_buffer<u8> ImRES1((int)(p_task_res->m_w * g_scale) * (int)(p_task_res->m_h / g_scale), (u8)255);
					SaveGreyscaleImage(ImRES1, wxString(Str), p_task_res->m_w * g_scale, p_task_res->m_h / g_scale);
				}

				//prevSavedFiles = p_task_res->m_SavedFiles;

				delete task_events[k];
				delete task_results[k];
			}
			catch (const exception& e)
			{
				SaveError(wxT("Got C++ Exception: got error in CreateClearedTextImages: ") + wxString(e.what()) + wxT("\n"));
			}
		}

		wait_all(begin(tasks), end(tasks));
	}

	//(void)wxMessageBox("dt: " + std::to_string(GetTickCount() - t1));

	if (!(g_pMF->m_blnNoGUI))
	{
		SaveToReportLog("CreateClearedTextImages: wxPostEvent THREAD_CCTI_END ...\n");
		wxCommandEvent event(THREAD_CCTI_END);
		wxPostEvent(g_pMF->m_pPanel->m_pOCRPanel, event);
	}
	else
	{
		g_IsCreateClearedTextImages = 0;
	}
}

void COCRPanel::ThreadCreateClearedTextImagesEnd(wxCommandEvent& event)
{
	std::unique_lock<std::mutex> lock(m_mutex);

	if (m_CCTIThread.joinable())
	{
		m_CCTIThread.join();
	}

	m_pCCTI->SetLabel(g_cfg.m_ocr_button_ccti_text);
	this->UpdateSize();

	m_pMF->m_pPanel->m_pSHPanel->Enable();
	m_pMF->m_pPanel->m_pSSPanel->Enable();
	
	m_pCES->Enable();
	m_pJOIN->Enable();
	m_pCSCTI->Enable();
	m_pCSTXT->Enable();	
	m_pDG->Enable();

	if ((g_RunCreateClearedTextImages == 1) && g_playback_sound)
	{
		SaveToReportLog("ThreadCreateClearedTextImagesEnd: trying to play sound ...\n");
		wxString Str = g_app_dir + wxT("/finished.wav");
		PlaySound(Str);
	}

	g_IsCreateClearedTextImages = 0;
}
